#pragma once

#include <list>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <thread>
#include "online.hpp"
#include "room.hpp"
#include "util.hpp"

// 创建一个阻塞队列

template <class T>

class match_queue
{

private:
    // 我们中间需要删除数据，因此没有使用queue
    std::list<T> _list;

    // 保证线程安全，我们在进行入队列和出队列的时候需要保证线程安全
    std::mutex _mutex;

    // 条件变量，主要是为了阻塞消费者，在使用时队列元素<2则阻塞
    std::condition_variable _cond;

public:
    int size()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        return _list.size();
    }

    bool empty()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        return _list.empty();
    }

    // 阻塞线程
    void wait()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _cond.wait(lock);
    }

    // 入队列并唤醒线程
    void push(const T &data)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _list.push_back(data);
        _cond.notify_all();
    }

    // 出队列
    bool pop(T &data)
    {
        std::unique_lock<std::mutex> lock(_mutex);

        if (_list.empty() == true)
        {
            return false;
        }

        data = _list.front();
        _list.pop_front();
        return true;
    }

    // 移除指定数据

    void remove(T &data)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _list.remove(data);
    }
};

class matcher
{
private:
    match_queue<uint64_t> _q_normal;

    match_queue<uint64_t> _q_high;

    match_queue<uint64_t> _q_master;

    std::thread _th_normal;

    std::thread _th_high;

    std::thread _th_master;

    room_manager *_rm; // 房间管理，将两个玩家加入房间，会调用online_manager把新的连接加入其中

    user_table *_ut; // 用户数据表，连接mysql数据库

    online_manager *_om; // 管理用户的在线链接（游戏大厅，游戏房间）

private:
    void handle_match(match_queue<uint64_t> &mq)
    {
        while (1)
        {
            // 判断队列人数是否大于2，小于2就阻塞
            if (mq.size() < 2)
            {
                mq.wait();
            }

            // 走下来代表人数足够匹配

            uint64_t uid1, uid2;
            bool ret = mq.pop(uid1);

            if (ret == false)
            {
                continue;
            }

            ret = mq.pop(uid2);

            if (ret == false)
            {
                //mq.push(uid1);
                //这里出现了错误应该是将原本的uid1重新加入this
                this->add(uid1);
                continue;
            }

            // 出队列成功，校验两个玩家是否都在线,如果有一个人掉线，就需要吧另一个人重现加入队列

            websocket_server::connection_ptr conn1 = _om->get_conn_from_hall(uid1);

            if (conn1.get() == nullptr)
            {
                this->add(uid2);
                continue;
            }

            websocket_server::connection_ptr conn2 = _om->get_conn_from_hall(uid2);

            if (conn2.get() == nullptr)
            {
                this->add(uid1);
                continue;
            }

            // 为两个玩家创建房间，并将玩家加入房间

            room_ptr rp = _rm->create_room(uid1, uid2);
            if (rp == nullptr)
            {
                this->add(uid1);
                this->add(uid2);
                continue;
            }

            // 对两个玩家响应

            Json::Value rsp;
            rsp["optype"] = "match_success";
            rsp["result"] = true;

            std::string body;

            ns_util::json_util::json_serialize(rsp, &body);
            conn1->send(body);
            conn2->send(body);
        }
    }

    void th_normal_entry() { return handle_match(_q_normal); }
    void th_high_entry() { return handle_match(_q_high); }
    void th_master_entry() { return handle_match(_q_master); }

public:
    matcher(room_manager *rm, user_table *ut, online_manager *om) : _rm(rm), _ut(ut), _om(om),
                                                                    _th_normal(std::thread(&matcher::th_normal_entry, this)),
                                                                    _th_high(std::thread(&matcher::th_high_entry, this)),
                                                                    _th_master(std::thread(&matcher::th_master_entry, this))
    {
    }

    bool add(uint64_t uid)
    {
        // 根据玩家的天梯分数来判定玩家的档次，添加到不同的匹配队列

        // 根据玩家id获取玩家信息

        Json::Value user;
        bool ret = _ut->select_by_id(uid, user);

        if (ret == false)
        {
            LOG_INF("获取玩家：%ld 信息失败！！", uid);

            return false;
        }

        int score = user["score"].asInt();

        // 根据分数匹配对手

        if (score < 2000)
        {
            _q_normal.push(uid);
        }
        else if (score < 3000)
        {
            _q_high.push(uid);
        }
        else
        {
            _q_master.push(uid);
        }
        return true;
    }

    bool del(uint64_t uid)
    {
        // 根据玩家的天梯分数来判定玩家的档次，添加到不同的匹配队列

        // 根据玩家id获取玩家信息

        Json::Value user;
        bool ret = _ut->select_by_id(uid, user);

        if (ret == false)
        {
            LOG_INF("获取玩家：%ld 信息失败！！", uid);

            return false;
        }

        int score = user["score"].asInt();

        // 根据分数，移除玩家

        if (score < 2000)
        {
            _q_normal.remove(uid);
        }
        else if (score < 3000)
        {
            _q_high.remove(uid);
        }
        else
        {
            _q_master.remove(uid);
        }
        return true;
    }
};
